package ar.edu.itba.pod.Legajo47342.communication;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Collection;
import java.util.Set;

import ar.edu.itba.pod.Legajo47342.Node;
import ar.edu.itba.pod.Legajo47342.communication.payload.DisconnectPayloadImpl;
import ar.edu.itba.pod.simul.communication.ClusterAdministration;
import ar.edu.itba.pod.simul.communication.ConnectionManager;
import ar.edu.itba.pod.simul.communication.Message;
import ar.edu.itba.pod.simul.communication.MessageType;

public class ClusterAdministrationImpl implements ClusterAdministration {

	private Node node;
	private String groupId = null;
	
	public ClusterAdministrationImpl(Node node) throws RemoteException {
		super();
		this.node = node;
		
		UnicastRemoteObject.exportObject(this, 0);
	}

	@Override
	public Iterable<String> addNewNode(String newNode) throws RemoteException {
		
		if (node.getClusterNodes().contains(newNode)) {
			System.out.println("Ya lo tengo!");
			return null;
		}
		
		if (this.groupId == null) {
			throw new IllegalStateException("The target node is not connected to a group");
		}

		String oId = node.getManager().getConnectionManager(newNode).
					getClusterAdmimnistration().getGroupId();
		
		System.out.println("OTHER "+oId+" local "+node.getGroupId());
		
		if (!oId.equals(node.getGroupId())) {
			throw new IllegalArgumentException("Origin groupId differs form destination groupId");
		}
		
		Set<String> randomNodes = node.getRandomNodes(newNode);
		
		node.getClusterNodes().add(newNode);
			
		System.out.println("Avisandole a algunos nodos ya conocidos, que entra: "+newNode);
		ConnectionManager m;
		ClusterAdministration a;		
		for (String n :randomNodes) {
			System.out.println("Avisando a: "+n);
			m = node.getManager().getConnectionManager(n);
			a = m.getClusterAdmimnistration();
			
			a.addNewNode(newNode);
		}
		
		//al devolver random nodes, hago que el que se conecto solo conozca a los que si o si ya saben que entro
		return randomNodes;
	}

	@Override
	public void connectToGroup(String initialNode) throws RemoteException {
		if (initialNode.equalsIgnoreCase(node.getNodeId())) {
			throw new IllegalArgumentException("Target Node equals connecting node");
		}
		
		if (isConnectedToGroup()) {
			throw new IllegalStateException("Already connected to a group");
		}

		ClusterAdministration remoteAdmin = 
			node.getManager().getConnectionManager(initialNode).getClusterAdmimnistration();
		System.out.println("group id "+remoteAdmin.getGroupId());
		this.groupId = remoteAdmin.getGroupId();
		
		Iterable<String> remoteNodes =  remoteAdmin.addNewNode(node.getNodeId());
		node.getClusterNodes().addAll((Collection<String>)remoteNodes);
		node.getClusterNodes().remove(node.getNodeId());
		node.getClusterNodes().add(initialNode);
		
		node.setGroupId(groupId);
		node.balance();
		
	}

	@Override
	public void createGroup() throws RemoteException {
		if (isConnectedToGroup()) {
			throw new IllegalStateException("Already connected to a group. Cannot create a new one");
		}
		groupId = "Group in: "+node.getNodeId();
		node.setGroupId(groupId);
		System.out.println(groupId);

	}

	@Override
	public void disconnectFromGroup(String nodeId) throws RemoteException {
		if(this.groupId == null){
			throw new IllegalStateException("no group is available, connot disconnect node");
		}
		if(!node.getClusterNodes().remove(nodeId) && !node.getNodeId().equals(nodeId)){
			throw new IllegalArgumentException("the node "+nodeId+" is not connected to this cluster");
		}
		
		System.out.println("Avisando que se va "+nodeId);
		
		//hacer mensaje broadcast aca
		node.getManager().getGroupCommunication().broadcast(
				new Message(nodeId, System.currentTimeMillis(), MessageType.DISCONNECT, new DisconnectPayloadImpl(nodeId)));
		
		groupId = null;
		
		node.getSimulManager().shutdown();
		node.getLoads().remove(node.getNodeId());
		node.getClusterNodes().remove(node.getNodeId());
		node.balanceWithAgents(((SimulationCommunicationImpl)node.getManager().getSimulationCommunication()).getDescriptors());
	}

	@Override
	public String getGroupId() throws RemoteException {
		return groupId;
	}

	@Override
	public boolean isConnectedToGroup() throws RemoteException {
		return groupId != null;
	}

}
